ASP.NET Coreでjsファイルをminifyする
ウェブサイトではBootstrapなどのCSSフレームワークや、jQueryなどのライブラリ、自作のcssファイルやjsファイルを各ページで適宜読み込んで使用します。
これらのファイルはHTMLファイルとともにクライアント(ブラウザ)側にダウンロードされるので、そのサイズは小さければ小さいほど良いことになります。
そこでminify
といって可読性のために挿入されている改行、半角スペース、タブといった空白文字を削除してサイズを小さくするということを行ったり、そもそも配布されているライブラリではjquery.min.js
というようにminify
されたバージョンを選択することができるようになっています。
一般的なライブラリであればminifyバージョンを選択すればよいですが、自作のものは何かしらのツールやサービスを使用してminifyする必要があります。
ASP.NET Core 2.1以降には任意のcss、jsファイルを設定ファイルに基づいて自動的にminifyする仕組みが含まれています。拡張機能やNuGet経由で何かをインストールする必要はありません。
準備は簡単で、プロジェクトフォルダの直下にbundleconfig.json
ファイルを作成し、そこに少しばかりの記述をするだけです。
例えば、テンプレートからASP.NET Coreウェブアプリケーションを作成した場合、wwwroot/js
フォルダの中にsite.js
というファイルが用意されていると思います。
最初は中はコメントだけですが、それらを消して以下のようにちょっとしたJavaScriptを書いておきました。
$('#myButton').on('click', function () { console.log('Yey.'); });
それでは、このファイルをminifyするための設定をbundleconfig.json
に書いてみます。
[ { "outputFileName": "wwwroot/js/site.min.js", "inputFiles": [ "wwwroot/js/site.js" ] } ]
シンプルで難しいことはないと思います。outputFileName
に出力ファイルのパス、inputFiles
にminifyしたいファイルのパスを記述するだけです。
そして保存する、とそのタイミングで指定したファイルが自動的にminifyされます。
ソリューションエクスプローラーで見てみると、以下のようにsite.js
にsite.min.js
がぶら下がるように生成されていることが確認できます。
あとはこのminifyされたファイルを読み込むようにHTML側で指定すればよいだけです。とても便利です。
複数のファイルをそれぞれminifyしたい場合は以下のようにします。
[ { "outputFileName": "wwwroot/js/site.min.js", "inputFiles": [ "wwwroot/js/site.js" ] }, { "outputFileName": "wwwroot/js/sidebarLinkActivate.min.js", "inputFiles": [ "wwwroot/js/sidebarLinkActivate.js" ] } ]
ところで、inputFiles
とあるので入力ファイルを複数指定して一つのminifyファイルにまとめることができます。
{ "outputFileName": "wwwroot/js/site.min.js", "inputFiles": [ "wwwroot/js/site.js", "wwwroot/js/sidebarLinkActivate.js" ] }
はてなブログでコードを折り返さずに表示する
はてなブログで技術系の記事を書くときにコードを貼り付けますが、デフォルトのままだと表示幅で折り返してしまい、読みにくくなります。
(追記) でもこれをやると,コード以外の通常の引用も折り返し無しになるため,とても見づらくなってしまいます...しまった.
しかし、他の方のブログでは折り返されずに表示されてて、そういう設定があるのかな、見つからないな、と思っていたら、これははてなブログのスタイルシートのカスタマイズの機能を使うということでした。↓
なるほど。
ダッシュボードのメニューからDesignを選択して、
Customizeを選択して、Stylesheetを開くとCSSを記述できる、と。
では折り返さないようにするCSSはどう書くのかというと、以下のとおり↓。
これをCSS記述欄に追加するだけでOk。
pre, code { max-height : 500px; overflow : scroll; white-space : pre !important; text-overflow : clip !important; }
DIとDIコンテナについて復習
DI:
DI(依存性の注入)は、あるクラス(Hogeクラス)の中で別のクラス(Fugaクラス)のインスタンスを利用する場合、FugaクラスをHogeクラス内でインスタンス化(new)するのではなく、コンストラクタ、プロパティ、メソッド経由でHogeクラスにインスタンスを渡すこと。
依存性というとわかりにくいけれど、「クラスHogeが利用する(依存する)オブジェクト」のこと。 それを内部で生成するのではなく外部から渡す(注入:injection)するので「依存性の注入:Dependency Injection」という。
コンストラクタ経由の注入(コンストラクタインジェクション)がもっとも良いといわれる。それは、Hogeクラスのインスタンス化時にHogeクラスが必要とするオブジェクトを全部コンストラクタに渡さなければならないので、プロパティ経由、メソッド経由のようにうっかり渡し忘れということがないし、Hogeを利用する(コーディングする)側としてHogeクラスがどのクラスを使っているのか、という依存関係を把握しやすいから。
DIパターンを使うことのメリット:
- クラス間の結合を低くできること
- それによってテスタビリティを高くできること
DIパターン(コンストラクタインジェクション)を使ったHogeクラスの実装は以下のようになる。
public class Hoge { private readonly Fuga _fuga; public Hoge(Fuga fuga) { _fuga = fuga; } public void DoSomething() { _fuga.SayHello(); } } //利用する側 static void Main() { var hoge = new Hoge(new Fuga()); hoge.SayHello(); }
このとき、FugaのインターフェースIFugaを作って、このインターフェース経由でDIするようにすると、テスタビリティが高くてなお良い。
public interface IFuga { public void SayHello(); } public class Fuga : IFuga { public void SayHello() { //実装 } } public class Hoge { private readonly IFuga _fuga; public Hoge(IFuga fuga) { _fuga = fuga; } public void DoSomething() { _fuga.SayHello(); } }
これでIFugaを実装するクラスならなんでもOKになるので、テスト用のクラス(例:FugaMockクラス)を作って渡すようにしても、Hogeクラスの実装に変更を加える必要がない。
DIコンテナ:
DIパターンを使うと、依存するクラスが多くなるとDI地獄(?)的なことになる。 (その場合はクラスの設計が悪い可能性が高いけれども)
var hoge = new Hoge(new Fuga(), new Bar(), new Foo()......);
これはまだいい方で、Fugaクラスが他のクラス(Misoクラス)に依存していて、MisoクラスがDaizuクラスに依存している、などとなるとDIパターンを使うと以下のようになる。
var hoge = new Hoge(new Fuga(new Miso(new Daizu), new Bar(), new Foo().... );
もう意味がわからない。
このクラス生成時の依存関係を解決する仕組み(フレームワークだったりライブラリ)がDIコンテナ。
DIコンテナ(の機能を持つクラスだよ)にクラスを登録しておき、DIコンテナ経由でインスタンス化することで、注入するインスタンスの生成などを自動的にやってもらうことができる。
例:擬似コード的ななにか
var container = new Container();//ライブラリやフレームワークが提供するクラス //まずはDIコンテナにクラスを登録する container.Register<IFuga, Fuga>();//interface経由なら container.Register<Miso>();//クラス直指定なら container.Register<Daizu>(); container.Register<Bar>(); container.Register<Foo>(); //Hogeクラスのインスタンス化 var hoge = container.Resolve<Hoge>();//必要なインスタンスの準備はこの1行で全て解決!
素晴らしいですね。
C#のDIコンテナのUnityを使った例↓
Prismを使ってXamarin.Formsアプリを作るときのメモ[引越記事]
はじめに
モバイルに限らずアプリケーションの開発というと難しく感じられるかもしれませんが、シンプルなアプリケーションであれば「こうしたいときはこう書く」みたいな基本的なパターンを知っておくだけで作ることができます。
それはVisual Studioのような統合開発環境の進歩や様々なライブラリ、フレームワークの充実、言語自体の進歩のおかげだったりします。
しっかりしたアプリケーションを作るためには専門的な知識が沢山必要だったりするのでしょうが、せっかくこんなにアプリケーション開発のハードルが下がってきたのだから、要点だけ押さえてアプリケーションの開発を楽しんでみていいんじゃないかな、と思います。
この記事では「イチから」というわけにはいきませんが、Xamarin.FormsとPrismというアプリケーション開発のフレームワークを使った、「こうしたいときはこう書く」を紹介したいと思います。
モバイルアプリの典型的な動作
モバイルアプリの動作は非常に大雑把に言って、「画面をタッチしたら画面が変わる」とか「画面をタッチしたら情報が更新される」とかです。
となると、イベント処理(ボタンを押す)、画面遷移(画面が変わる)、データバインディング(情報の更新)の基本的な書き方を知っていればシンプルなアプリケーションが作れる、と思います。
そこで、この記事では以下の5つの項目について書きます。
- データバインディング
- イベント処理
- 画面遷移
- データの受け渡しを伴う画面遷移
- 状態によるボタンの有効無効
前提
- Windows 10
- Visual Studio 2017
- Prism Template Pack インストール済み
- Prism Blank App(Xamarin.Forms)でプロジェクト作成済み
1.データバインディング
バインディングしたいプロパティはpublic
にします。
そしてsetter
内でSetProperty
メソッドを使います。
このメソッドがプロパティの値に変化があったときにView側に通知して表示が変化します。
private string _title; public string Title { get => _title; set => SetProperty(ref _title, value); }
Title="{Binding Title}"
プロパティが持つプロパティをバインディングしたい場合
View側で目的のプロパティまでのパスを「.」でつなげていきます。
以下の例は自作のGeolocaionクラスで、このクラスはLocation
プロパティを持っていて、そのLocation
プロパティはLatitude
プロパティを持っています。
Geolocation
型のプロパティのプロパティであるLatitude
をバインディングしたい場合は、Text="{Binding Path=Geolocation.Location.Latitude}"
とします。
Geolocation
private Geolocation _geolocation; public Geolocation Geolocation { get => _geolocation; set => SetProperty(ref _geolocation, value); }
<Label Text="{Binding Path=Geolocation.Location.Latitude}" FontSize="15" HorizontalTextAlignment="End" HorizontalOptions="FillAndExpand"/>
配列やListなどのCollectionをバインディングするとき
ObservableCollection
を使います。
以下のようにObservableCollection
型のプロパティを用意し、それをリスト形式の表示を行うUI要素(XamarinであればListView、WPFならListBoxなど)にバインディングします。
public ObservableCollection<LocationInformation> Targets { get; set; } = new ObservableCollection<LocationInformation>();
<ListView AbsoluteLayout.LayoutFlags="All" AbsoluteLayout.LayoutBounds="0,0,1,1" HasUnevenRows="True" ItemsSource="{Binding Path=Targets}"> //以下略
このObservableCollection
型のプロパティに対して、Add
やRemoveAt
やClear
などのメソッドで変更を加えると、それがView側に反映されます。
2.イベント処理
コードビハインドのxaml.cs
ファイルにイベントハンドラを書くこともできますが、ここではViewModel側にイベントハンドラを書く方法を紹介します。
Buttonが押されたとき
<Button Text="text" Command="{Binding Path=MyCommand}" />
public Command MyCommand => new Command(() => { //ボタンが押されたときの処理を書く });
Buttonが押されたとき、文字列をパラメータとして渡す
CommandParameter
に指定した文字列がViewModel側のCommandが呼び出されるときに引数として渡される。
以下の例ではView側で遷移先のViewを文字列で指定できるようにしている。
<Button Text="text" Command="{Binding Path=MyCommand}" CommandParameter="SecondPageView"/>
public Command<string> MyCommand => new Command<string>(name => { _navigationService.NavigateAsync(name); });
Button以外のUI要素がタップされた場合
Buttonの場合は押されたときにCommand
プロパティにバインディングしたCommandが実行されますが、Button以外のUI要素がタップされたときはGestureRecgnaizers
プロパティを使います。
Labelがタップされた場合
<Label Text="{Binding Path=TargetInfo.Name}" FontSize="40" HorizontalOptions="FillAndExpand" HorizontalTextAlignment="Center"> <Label.GestureRecognizers> <TapGestureRecognizer Command="{Binding Path=NavigateCommand}" CommandParameter="SelectTargetPage"/> </Label.GestureRecognizers> </Label>
ListViewでアイテムがタップされた場合
EventToCommandBehavior
を使います。
そのUI要素の任意のイベントを拾いたい場合に使います。
EventName
には拾いたいイベント名を指定します。
Command
にはCommand型のプロパティを指定します。この例では選択されたアイテムのデータを引数として渡したいので、プロパティの定義ではCommand<T>
クラスを使います。
EventArgsParameterPath
プロパティにはListViewが持つプロパティのうち、Commandに渡したいプロパティの名前を指定します。この例では選択されたアイテムが格納されるSelectedItem
を指定します。
EventName
もEventArgsParameterPath
も、指定した文字列のイベントやプロパティをPrismが探して使用してくれます。
<ListView.Behaviors> <behaviors:EventToCommandBehavior EventName="ItemSelected" Command="{Binding Path=ItemSelectedCommand}" EventArgsParameterPath="SelectedItem"/> </ListView.Behaviors>
public Command<LocationInformation> ItemSelectedCommand => new Command<LocationInformation>(targetInfo => { //アイテムが選択されたときの処理を書く。 //引数targetInfo(名前は何でも良い)には選択されたアイテムのデータが入っている。 //この例では`LocationInformation`型のコレクションをListViewにバインディングしているので、targetInfoは`LocationInformation`型のデータです。 });
3.画面遷移
上の例で出てきたとおり。
NavigateAsync
メソッドで指定したViewに遷移できる。
_navigationService.NavigateAsync("遷移先のView名");
じゃあこの_navigationService
はどこからきたのかというと、そのViewModelのコンストラクタの引数として渡されてくるので、それをprivateなフィールドに入れてViewModel内で使えるようにしている。
_navigationService
をViewModelのコンストラクタに渡すのはPrismがDIコンテナを使ってやってくれる。
private INavigationService _navigationService; public SelectTargetPageViewModel(INavigationService navigationService) { _navigationService = navigationService; }
データの受け渡しのある画面遷移
NavigateAsync
には受け渡したいデータを指定できるOverrideがあるので、それを使います。
NavigationParameters
はDictionaryと同じようにキーと値のペアで渡したいデータ(任意のオブジェクト)を格納していきます。
そして、NavigateAsync
メソッドで遷移先のView名とともにNavigationParameters
型のオブジェクトを渡します。
var parameters = new NavigationParameters { { "キー名", targetInfo}, }; _navigationService.NavigateAsync(nameof(TargetDetailPage), parameters);
受け取る側のViewModelでは以下のようにしてデータを受け取ります。
OnNavigatingTo
(またはOnNavigatedTo
)メソッドで受け取ります。
引数parametersに遷移先でNavigateAsync
メソッドに渡したNavigationParameters
オブジェクトが入ります。
それをキー名で取り出してas
で型変換して利用します。
public void OnNavigatingTo(INavigationParameters parameters) { var targetInfo = parameters[nameof(LocationInformation)] as LocationInformation; if (targetInfo == null) { this.Title = "New"; return; } this.TargetInfo = targetInfo; this.Title = "Edit"; }
4.画面遷移時の処理の流れ
画面遷移するときに遷移元と遷移先で後始末や準備、遷移元からのデータの受取などをしたい場合は、遷移元と遷移先でViewModelクラスにINavigationAware
インタフェースを継承して遷移時に実行されるメソッドを実装する。
INavigationAware
は以下の3つのメソッドの実装が強制されます。
そして、この順番に実行されます。
- OnNavigatingTo(遷移先:描画前)
- OnNavigatedFrom(遷移元)
- OnNavigatedTo(遷移先:描画後)
データの受け渡しがある画面遷移の場合、遷移先のOnNavigatingTo``OnNavigatedTo
でデータを受け取る。
他に、デバイスのホームボタンや戻るボタンが押されたときの動作は以下のとおりです。
ルートページ(例えばMainPage)でデバイスの戻るボタンが押されたとき
- OnSleep(ページ側)
- App.OnSleep
復帰時
- MainPageコンストラクタ
- OnNavigatingTo
- OnNavigatedTo
- App.OnInitialized
デバイスのホームボタンが押されたとき
- OnSleep(ページ側)
- App.OnSleep
復帰時
- OnResume(ページ側)
- App.OnResume
アプリ起動時
- MainPageコンストラクタ
- OnNavigatingTo
- OnNavigatedTo
- App.OnInitialized
上スワイプでアプリ切替時(アプリ選択画面)
- OnSleep(ページ側)
- App.OnSleep
復帰時
- OnResume(ページ側)
- App.OnResume
電源ボタンを押したとき
- OnSleep(ページ側)
- App.OnSleep
復帰時
- OnResume(ページ側)
- App.OnResume
ナビゲーションバーの戻るボタン
- OnNavigatedFrom
- OnNavigatedTo
GobackAsyncメソッドで戻る場合
- OnNavigatingTo
- OnNavigatedFrom
- OnNavigatedTo
NavigateAsyncメソッドで遷移した場合
- 遷移先コンストラクタ
- OnNavigatingTo
- OnNavigatedFrom
- OnNavigatedTo
5.状態によるボタンの有効、無効
DelegateCommand
を使います。
public ICommand MyCommand => new DelegateCommand(() => { //実行したい処理 }, () => { //Buttonの有効、無効を判定したい条件 //例えば、何かしらかのUI要素にバインディングしている値が特定の値かどうか }). ObservesProperty(() => /*有効無効の判定に使いたいプロパティ*/);
例えば、Entry(WPFでいうTextBox)コントロールにName
プロパティをバインディングしていて、そのEntryに入力がなければButtonは無効、入力があれば有効、というような動作をさせたい場合は以下のようにする。
public ICommand MyCommand => new DelegateCommand(() => { //実行したい処理 }, () => { //Buttonの有効、無効を判定したい条件 return string.IsNullOrWhiteSpace(this.Name); }). ObservesProperty(() => this.Name);
複数のプロパティを条件としたい場合、例えば2つのEntryコントロールの両方に入力がある場合だけButtonを有効にする場合は以下のようにする。
public ICommand MyCommand => new DelegateCommand(() => { //実行したい処理 }, () => { //Buttonの有効、無効を判定したい条件 return string.IsNullOrWhiteSpace(this.Name) && string.IsNullOrWhiteSpace(this.Age); }) .ObservesProperty(() => this.Name) .ObservesProperty(() => this.Age);
おわりに
ざっくり大雑把にXamarin.FormsとPrismを使ってモバイルアプリケーションを開発するときの基本的な「こうしたいときはこう書く」を紹介してみました。
たくさんあるようなないような内容でしたが、個人的にはこれだけ知っていればそれなりのアプリケーションを作れると思います。
説明をもっとわかりやすく書けたらいいなとおもいつつ、今回のところはこのへんにしておきたいと思います。
ASP.NET Coreでファイルをアップロードする
ViewModelにIFormFile型のプロパティを用意する。
using Microsoft.AspNetCore.Http; namespace MyApp.ViewModels.UploadFile { public class UploadFileViewModel { public IFormFile SelectedFile { get; set; } } }
View側でアップロードするファイルを選択するFormを用意する。
<form method="post" asp-action="upload" enctype="multipart/form-data"> <div class="form-group row"> <div class="col-8"> <div class="custom-file"> <input asp-for="SelectedFile" class="form-control custom-file-input" /> <label class="custom-file-label">成績ファイルを選択してください</label> </div> </div> </div> <div class="form-group row"> <div class="col-8"> <button class="btn float-right">読み込み</button> </div> </div> </form>
ControllerにActionメソッドを用意する。
[HttpPost] public IActionResult Upload(UploadFileViewModel model) { //ここでなにかする return View(); }
これでUpload
アクションメソッドのパラメータのmodel
にPostされたデータ(ファイル)が渡されるので、これをFileStreamでサーバー上に保存したりなんかしたり。
動作確認していないので上のコードが動くかわからない。けれど、こういう感じ。
Xamarin.FormsでPrism Template Packで作ったプロジェクトでHot Reloadを試すまで
環境
- Windows 10 1903
- Visual Studio 2019 16.3 preview 16.3
- Xamarin.Forms 4.2.0.709249
目的
目的はXamarin.FormsにPrism Template Packで作成したプロジェクトでHot Reload機能を試すことです。
結論から言うと、Prism Template Packのテンプレートから作成したプロジェクトのNuGetパッケージをすべて更新するというだけのこと。
Prism Template Packをインストールする
素のXamarin.FormsプロジェクトからPrismを適用することもできますが、Prismのプロジェクトテンプレートを使えば開発を開始しやすいのでインストールすることにします。
Visual Studioで拡張機能としてインストールします。
さて、インストールするとまず最初に↓のメッセージがVisual Studio上部に表示されたりします。
Hot Reload機能を使うためにはバージョンが古すぎる、ということです。
確認してみると、Prism Template Packageで使用されているXamarin.Formsのバージョンは3.2ぐらいです。
Xamarin.Formsの更新
NuGetでXamarin.Forms単体をインストールしようとすると、エラーが出てAndroid側のXamarin.Formsの更新に失敗します。
片方だけ↓
これはAndroid側のプロジェクトで参照されているパッケージのバージョンが古いためです。
ということで、NuGetでインストール済みのパッケージの更新を適用します。
Updateタブですべて選択して更新します。
これでXamarin.Formsの更新が完了し、Hot Reload機能が使えるようになります。
あとはHot Reload機能を有効にすればOkです。
Xamarin.FormsでHot Reloadを試す
環境
- Visual Studio 2019 16.3 preview2
- Windows 10 1903
- Xamarin.Forms 4.2.0.673161-pre3
こんなときは
こんなメッセージが出たときは、
Xamarin.Forms 4.2.0.673161-pre3をインストール。
Include prerelease
にチェックをつけること。
Hot Reload機能を有効に
Tools > options > Xamarin > Hot Reload とクリックして、
Enable Xamarin Hot Reload(Preview)
にチェックをつける。
以上。