ASP.NET MVC 5 でクライアントライブラリの管理を nuget から bower + grunt-bower-task に移行した #aspnetjp
VisualStudio 勢として高見の見物していたライブラリ管理*1だったけど、.NET 周りはいいとして JS, CSS 周りは NuGet に限界を感じ始めたので移行しました。
まだまだ全然わかんないんだけど、とりあえず触ってみたとこ。なので説明は省いて手順だけ。
× MVC 4、○ MVC 5(2016/01/20 追記)
ASP.NET 5、ASP.NET MVC 6 と色々なバージョンが頭でごっちゃになって間違ってました。てへ
ASP.NET MVC 5 でクライアントライブラリの管理を nuget から bower + grunt-bower-task に移行した
環境
- Visual Studio 2013(bower や grunt-bower-task を動かすのに環境構築が必要)
- Visual Studio 2015(上記を標準サポート)
- ASP.NET Web アプリケーション MVC 5 テンプレート(説明の都合上。適宜、選択してください。)
目標
- ASP.NET Web アプリケーション MVC 5 テンプレート を踏襲したファイル配置。
- ASP.NET MVC 6 や業界標準は(わからないから)考えないこととする。
- angularjs を使った簡単な Web サイトを作るためのライブラリ準備
- bootstrap には日本語も美しく表示できるテーマ「honoka」から派生した「umi」を採用
手順
- NuGet で管理している JS, CSS をアンインストール
- package.json を記述(npm)
- bower.json を記述(bower)
- gruntfile.js を記述(grunt)
- タスクランナーで実行
NuGet で管理している JS, CSS をアンインストール
- packages.config から記述を消して、Scripts\_references.js ファイル以外の Scripts, Contents, fonts フォルダー以下のファイルを削除
- bootstrap
- jQuery
- jQuery.Validation
- Microsoft.jQuery.Unobtrusive.Validation
- Modernizr
- Respond
package.json を記述(npm)
- [プロジェクトを右クリック] > [追加] > [新しい項目] > [package.json]
{ "version": "0.0.0", "name": "example", "private": true, "devDependencies": { "grunt": "~0.4.5", "grunt-bower-task": "~0.4.0" } }
bower.json を記述(bower)
- [プロジェクトを右クリック] > [追加] > [新しい項目] > [bower.json]
{ "name": "example", "private": true, "dependencies": { "jquery": "2.2.0", "bootstrap-sass": "3.3.6", "Umi": "3.3.6-1", "angular": "1.4.8", "angular-messages": "1.4.8", "angular-route": "1.4.8", "angular-loading-bar": "0.8.0", "angular-hotkeys": "chieffancypants/angular-hotkeys#~1.6.0" }, "devDependencies": { "DefinitelyTyped": "https://github.com/borisyankov/DefinitelyTyped.git" }, "exportsOverride": { "jquery": { "js": "dist/*.*" }, "bootstrap-sass": { "bootstrap.scss": "assets/stylesheets/bootstrap", "bootstrap.fonts": "assets/fonts/bootstrap/*.*", "js": "assets/javascripts/bootstrap.*" }, "Umi": { "umi": "scss" }, "angular": { "css": "angular-csp.css", "js": "angular.*" }, "angular-messages": { "js": "angular-messages.*" }, "angular-route": { "js": "angular-route.*" }, "angular-loading-bar": { "css": "build/*.css", "js": "build/*.js" }, "angular-hotkeys": { "css": "build/*.css", "js": "build/*.js" }, "DefinitelyTyped": { "ts": [ "jquery/*.d.ts", "bootstrap/*.d.ts", "angularjs/*.d.ts", "angular-loading-bar/*.d.ts", "angular-hotkeys/*.d.ts" ] } } }
TypeScripts の型定義「DefinitelyTyped」はすべてのライブラリ分を DL してくる。横着しすぎでしょ感あるけど実際楽!
gruntfile.js を記述(grunt)
- [プロジェクトを右クリック] > [追加] > [新しい項目] > [gruntfile.js]
module.exports = function (grunt) { grunt.initConfig({ bower: { install: { options: { targetDir: "", cleanTargetDir: false, layout: function (type, component, source) { if (type === "js") { return "Scripts"; } else if (type === "ts") { return "Scripts/typings/" + source.match(/\\[^\\]*(?=\\[^\\]*$)/)[0].replace("\\", ""); } else if (type === "css") { return "Content"; } else if (type === "umi") { return "Content"; } else if (type === "bootstrap.scss") { return "Content/honoka/bootstrap"; } else if (type === "bootstrap.fonts") { return "fonts"; } else { return "__untyped__"; } } } } } }); grunt.registerTask("default", ["bower:install"]); grunt.loadNpmTasks("grunt-bower-task"); };
タスクランナーで実行
- [表示] > [その他のウィンドウ] > [タスク ランナー エクスプローラー] > [bower:install]
参考
- Visual Studio 2013 で Bower を使う - miso_soup3 Blog
- パッケージ マネージャー Bower と Visual Studio 2015 / ASP.NET 5 プロジェクト | VS 魂 | Channel 9
- タスクランナー Grunt, Gulp と Visual Studio 2015 / ASP.NET 5 プロジェクト | VS 魂 | Channel 9
- ASP.NET 5 と Visual Studio 2015 では Bower と Grunt が標準になるみたいなので勉強してみた - しばやん雑記
- bowerでインストールしたファイルの配置を設定するにはgrunt-bower-taskが便利 - my story blog
- Umiを作ってUmiに移行した話 | Sinji's View
*1:Web 業界が色々なツールとそれぞれの config.json 書いてるのが正直わからない
カスタムモデルバインダーのすゝめ #aspnetjp
この記事はASP.NET Advent Calendar 2015 - Qiitaに参加しています。17日の担当です。前日は id:minato128 さんでした。
カスタムモデルバインダーのすゝめ
ASP.NET MVCではモデルバインダーという仕組みで、リクエストの値をモデルに紐づけてくれます。自分でFormの中身やクエリーストリングを見たり、パースしたりしなくてもいい素晴らしい仕組みです。お手軽さ、直感さ、大好き。
そして、もちろんカスタマイズできます。和暦 "H27/12/17" を DateTime
型にしたいなんて闇もモデルバインダーで済ませられれば捗ります。
_________________________ <○√ ∥ ←モデルバインダー くく
作成方法
- DefaultModelBinder を継承
- BindModel をオーバーライド
- カスタムモデルバインダーの登録
DefaultModelBinder を継承 ~ BindModel をオーバーライド
DefaultModelBinder
を継承して BindModel
をオーバーライドするだけです。その中でモデルステート用の設定を呼び出す等の多少のお作法がある程度。
今回は "1,000" というカンマ区切りの数値文字列を数値型で受け取れるようにしてみます。*1
DefaultModelBinder を継承
数値型の部分をジェネリックにして、型制約を適当に。*2
/// <summary> /// 1000 の桁で区切られた数字のブラウザー要求を数値型に対応付けます。 /// </summary> /// <typeparam name="T">対応付ける数値型。</typeparam> public class ThousandsSeparatorNumberBinder<T> : DefaultModelBinder where T : struct, IConvertible {}
BindModel をオーバーライド
多少のお作法を守りながら変換するだけ。唐突に登場している Parser
は後述。
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext) { var valueProviderResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName); if (valueProviderResult == null) { return base.BindModel(controllerContext, bindingContext); } // モデルステートの設定 bindingContext.ModelState.SetModelValue(bindingContext.ModelName, valueProviderResult); // 値の文字列 var valueString = valueProviderResult.AttemptedValue; // 以下、変換処理 if (string.IsNullOrWhiteSpace(valueString)) { return null; } try { return Parser(valueString); } catch (Exception ex) { bindingContext.ModelState.AddModelError(bindingContext.ModelName, ex); return null; } }
カンマ区切りの数値文字列を数値型に変換
int.Parse
に NumberStyles
フラグを渡すことで簡単に実現できます。ただ、数値型は複数あるのでそこは頑張ります。パース時のオーバーフロー例外を期待するとか考慮するとそれぞれきっちり。
/// <summary> /// 送信された文字列の変換に使うパーサー。 /// </summary> private static readonly Func<string, object> Parser; /// <summary> /// パーサーを初期化します。 /// </summary> static ThousandsSeparatorNumberBinder() { // short, long は int と同様、float, double は decimal と同様なので割愛 var parsers = new Dictionary<Type, Func<string, object>> { { typeof(int), (string value) => int.Parse(value, NumberStyles.Integer | NumberStyles.AllowThousands) }, { typeof(decimal), (string value) => decimal.Parse(value, NumberStyles.Number) } }; Parser = parsers[typeof(T)]; }
カスタムモデルバインダーの登録
アプリケーション全体、または、コントローラーのアクションで登録します。今回はプリミティブな型向けなのでアプリケーション全体で登録するのが良いかと思います。
// モデルバインダーの登録 // グローバルで一括登録するならキー型は nullable も忘れずに! // カスタムバインダー側は nullable でなくておk。 // コントローラーやアクションでの個別利用なら適宜どうぞ。 ModelBinders.Binders.Add(typeof(int), new ThousandsSeparatorNumberBinder<int>()); ModelBinders.Binders.Add(typeof(int?), new ThousandsSeparatorNumberBinder<int>());
結果
やったね!
バトンタッチ
Visual Studioをセーフモードで起動する方法
Visual Studioをセーフモードで起動する方法
Visual Studioには実はセーフモードがあります。/SafeMode
オプションをつけるとセーフモードで起動できます。
devenv /SafeMode
/SafeMode (devenv.exe) - MSDN
「パス通ってる訳ないじゃん!」と思った方にはこちら。いい感じにパスが通ってるコマンドプロンプト「開発者コマンド プロンプト for VS2013」。
そもそも「めんどくさいよ!」と思った方にはこちら。タスクバーjump listの拡張をしてくれる「VSCommands」。
Solution Badges for recent solutions are displayed in Visual Studio Jump List for easy access
Jump List - VSCommands
セーフモードで起動するとどうなるのか
拡張機能や設定の読み込みをスキップ(無効化)して起動されます。
セーフモードはどういう時に使うのか
拡張機能やupdateなどのなんらかの原因で起動時に例外が出るようになってしまった場合、起動に時間がかかりすぎて起動してるのかわからないような場合*1に、藁にも縋る思いで使います。
拡張機能が原因の場合はこの方法でVisual Studioが起動するようになるので、その間に該当の拡張機能をアンインストールすることで復旧できるかと思います。それ以外の場合でも設定のリセットなどを試せたり、Visual Studio自体のインストールは済んでいるといった切り分けができることでしょう。
先月くらいに、奇しくもVSCommandsが原因でVisual Studioが起動しなくなる事件がありました。その時にVSCommandsのQ&Aに書き込んでみたら結構役立って頂けたようなので今回ブログにまとめました。
おまけ:VSCommandsが便利
ところで、VSCommands とても便利です。機能がありすぎて使いこなせてない & 紹介できないんですが挙げるとすれば、3つ。
- Solution Badges
- ウィンドウタイトルにブランチ名出してくれる
- Solution Explorer Enhancements
- ソリューションエクスプローラーを拡張してくれる
- 特に[Group Items]でファイル同士で親子にできるのは*.min.jsなどをまとめるのに便利
- Touch Support
- コードエディタをタッチで操作できる
他に「実はhogehogeもVSCommandsで便利なんだぜ」とかあったら教えてください。
*1:初代surface proにセットアップしてみた時に体験。原因不明。それ以来は快適!